package util;

import java.math.BigInteger;

/**
 * This is a container class to store points. It also gives several methods to get information
 * about this point and in relation to other points.
 * @author Ralf
 *
 */
public class Point {

	private BigInteger x;
	private BigInteger y;
	private boolean isInfinite;

	/**
	 * Constructor which creates a PointOfInfinity-point
	 */
	public Point() {
		setToPointOfInfinity();
	}
	
	/**
	 * Constructor with valid values for this point.
	 * @param x the x to set
	 * @param y the y to set
	 */
	public Point(BigInteger x, BigInteger y) {
		setValidCoordinates(x, y);
	}
	
	/**
	 * Constructor with Strings as parameters for BigInteger-Initialization
	 * @param x the x to set
	 * @param y the y to set
	 */
	public Point(String x, String y) {
		this(new BigInteger(x), new BigInteger (y));
	}	

	/**
	 * This method return a regular expression, which stores the digits and letters available
	 * for number in the given base.
	 * @param base the base or the radix of the used numbers
	 * @return a regular expression in the form of "[0-9a-fA-F]" for the base 16
	 */
	private String getRegexForBase(int base) {
		if (base <= 1) throw new IllegalArgumentException("Invalid base <= 1");
		if (base > 37) throw new IllegalArgumentException("Invalid base > 36");
				
		if (base <= 10) {
			return "[0-" + (base-1) + "]";
		}
		if (base == 11) 
			return "[0-9aA]";
		
		return "[0-9a-" + (char)('a' + base-11) + "A-" + (char)('A' + base-11) + "]";
	}
	
	/**
	 * Constructor which creates a point given a string, formatted like the Point.toString() result.
	 * Everything else results in a Point of Infinity.
	 * @param pointRepresentation a String representation of a valid point
	 */
	public Point(String pointRepresentation) {
		// check for basic structure
		if (!pointRepresentation.matches("\\(.*,.*\\)\\[.*\\]")) {
			// String must be formatted as "(<optional sign><number>,<optional sign><number>)[<base>]"
			setToPointOfInfinity();
		} else {
		
			// get some information out of the string
			// get the used base
			String base = pointRepresentation.split("[\\[|\\]]")[1];
			//base = base.substring(0, base.length()-1);
			int baseNumber = Integer.parseInt(base);
			// get the regex for numbers using that base
			String regex = getRegexForBase(baseNumber);
			String fullRegex = "\\([+|-]?" + regex + "+,[+|-]?" + regex + "+\\)\\[[0-9]+\\]";
			// compare the string
			if (!pointRepresentation.matches(fullRegex)) {
				setToPointOfInfinity();
			} else {
				String tmp = pointRepresentation.substring(1, pointRepresentation.length() - 1);
				String[] tmpArray = tmp.split("[,|\\)]");
				setValidCoordinates(new BigInteger(tmpArray[0], baseNumber), new BigInteger(tmpArray[1], baseNumber));
			}
		}
	}
	
	/**
	 * Copy Constructor using another point as parameter
	 * @param p another point, whose values are used for the initialization of this point 
	 */
	public Point(Point p) {
		if (p.isPointOfInfinity()) {
			setToPointOfInfinity();
		} else {
			setValidCoordinates(p.getX(), p.getY());
		}
	}
	
	/**
	 * Returns the x value of this point.
	 * @return the x value of this point
	 * @throws PointOfInfinityException if this point is PointOfInfinity
	 */
	public BigInteger getX() {
		if (!isInfinite)
			return x;
		throw new PointOfInfinityException("PointOfInfinityException in getX()");
	}

	/**
	 * Returns the y value of this point.
	 * @return the y value of this point
	 * @throws PointOfInfinityException if this point is PointOfInfinity
	 */
	public BigInteger getY() {
		if (!isInfinite)
			return y;
		throw new PointOfInfinityException("PointOfInfinityException in getY()");
	}
	
	/**
	 * Sets valid values for this point and revokes a possible isPointOfInfinity-flag.
	 * @param x the valid x value for this point
	 * @param y the valid y value for this point
	 */
	private void setValidCoordinates(BigInteger x, BigInteger y) {
		this.x = x;
		this.y = y;
		this.isInfinite = false;		
	}
	
	/**
	 * Sets this point to PointOfInfinity. The values are set to zero.
	 */
	private void setToPointOfInfinity() {
		x = BigInteger.ZERO;
		y = BigInteger.ZERO;
		isInfinite = true;
	}
	
	/**
	 * Checks, if this point is the PointOfInfinity. 
	 * @return
	 */
	public boolean isPointOfInfinity() {
		return isInfinite;
	}
	
	/**
	 * Checks for equality of a given point to this point.
	 * Two points are considered equal, if the values are the same or both are PointOfInfinity.
	 * @param p another point to compare with
	 * @return true, if points are equal
	 */
	public boolean isEqual(Point p) {
		if (isInfinite != p.isPointOfInfinity())
			return false;
		if (isInfinite == true) // and implicitly p.isPointOfInfinity() == true 
			return true;
		if (!x.equals(p.getX()))
			return false;
		if (!y.equals(p.getY()))
			return false;
		return true;
	}
	
	/**
	 * Checks, if a point is equal to this point with the modulus operation.
	 * @param p another point
	 * @param m a modulus
	 * @return true, if the x-values are equal and the y-values mod m are equal
	 */
	public boolean isModulusEqual(Point p, BigInteger m) {
		if (isInfinite != p.isPointOfInfinity())
			return false;
		if (isInfinite == true) // and implicitly p.isPointOfInfinity() == true 
			return true;
		if (!x.mod(m).equals(p.getX().mod(m)))
			return false;
		if (!y.mod(m).equals(p.getY().mod(m)))
			return false;
		return true;		
	}
	
	/**
	 * Generates a new point, which is the negative of this point.
	 * A negative point has the same x-value, but the negative y-value.
	 * @return a new point, which is the negative of this point
	 */
	public Point getNegative() {
		if (isInfinite)
			throw new PointOfInfinityException("PointOfInfinityException in getNegative()");
		
		return new Point(x, y.negate());
	}
	
	/**
	 * Checks, if a point is the negative of this point.
	 * @param p another point to compare with
	 * @return true, if both points are valid and p is the negative of this point
	 */
	public boolean isNegative(Point p) {
		if (p.isPointOfInfinity() || isInfinite)
			return false;
		
		return isEqual(p.getNegative());
	}
	
	/**
	 * Checks, if a point is the negative of this point using a modulus
	 * @param p another point to compare with
	 * @param m modulus
	 * @return true, if both points are valid, m positive and p is the negaive of this point
	 */
	public boolean isModulusNegative(Point p, BigInteger m) {
		if (p.isPointOfInfinity() || isInfinite)
			return false;
		
		if (m.signum() <= 0) {
			throw new IllegalArgumentException("m <= 0");
		}
		if (!this.getX().equals(p.getX()))
			return false;
		
		return this.y.add(p.getY()).mod(m).equals(BigInteger.ZERO);		
	}

	/**
	 * Returns the negative of this point using a given modulus.
	 * This means the y-value is always positive.
	 * Also the y-values of this point and the result add up to m.
	 * @param m a modulus
	 * @return the negative of this point
	 */
	public Point getModulusNegative(BigInteger m) {
		return new Point(this.x, this.y.mod(m));
	}
	
	/**
	 * Returns a String representation of this point.
	 * The format is "(x, y)" or "(Point of Infinity)".
	 * @param base the base used for the number representation
	 * @return this point as a string under the given base
	 */
	public String toString(int base) {
		if (!isInfinite)
			return "(" + x.toString(base) + "," + y.toString(base) + ")[" + base + "]";
		
		return "(Point of Infinity)";
	}

	/**
	 * Returns a String representation of this point.
	 * The format is "(x, y)" or "(Point of Infinity)".
	 * @return this point as a string base 16
	 */
	public String toString() {
		return toString(16);
	}
}
